iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 3
3
Modern Web

給初入JS框架新手的React.js入門系列 第 3

【React.js入門 - 03】 開始之前應該要知道的DOM和ES6

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


這個系列和我在12屆鐵人賽的React.js系列文,已經在修訂後和深智數位合作出版成實體書,在天瓏開始預購了,想學React的朋友可以參考看看:
https://www.tenlong.com.tw/products/9789860776188?list_name=srh

當初自己從ES5在進入到框架前,對於前端的觀念僅止於能用html/css/js實現想要呈現的介面。然而在學習React時,有一些重要的事情和ES6的新語法需要先了解。我會在這篇簡短帶過相關的知識,但由於不是這次的重點,建議可以搭配其他資源,了解相關細節。

DOM(Document Object Model)

如果要你說明「網頁」是什麼,你會怎麼描述呢?一般人對於網頁的認知,通常是眼睛看的到的「UI」。然而對接觸過前端技術的人,可能會認為網頁是一個包含:

  • 元素 : 如: button、div
  • 事件 : 如: 點擊、輸入
    ...等等族繁不及備載。總結來說,就像是一個「提供很多可以操作的介面的程式」。

事實上,這支程式的架構就像是一棵樹、一份整理完善的文件,支幹底下有分支(例如: 元素類別中有按鍵)、分支上有多個樹葉(例如: 按鍵有提供點擊功能)...。這樣的結構,在程式語言中又稱為是「物件導向(Object-orient,簡稱OO)」結構。詳情請見MDN。

因此,我們把這個 HTML、XML 和 SVG 文件的程式介面,稱為文件物件模型(Document Object Model, DOM)。

這些常用的語法就是在操作DOM:

  • document.getElementById就是用id在向DOM取得元素。
  • document.getElementById().scrollTop=....就是在修改元素在DOM的scrollTop。

ES6 - 新的宣告方法

過去我們宣告變數/函式時,是透過var關鍵字宣告。但是這種宣告方式產生了兩個問題:

  1. var是全域(global)的,也就是即使在其他scope也會存在。
  2. 沒有辦法保護var,也就是「不能強制不能被改變」。

為了解決這兩個問題,新關鍵字letconst就出現了。前者就是只能在宣告的scope存在的變數,後者是只能在宣告的scope存在且不能被改變值的變數。和var的使用方法幾乎一樣,letconst的使用方法是:

let 變數名稱;
let 變數名稱 = 初始化值;
const 變數名稱 = 初始化值;

注意因為const變數宣告後不能被修改值,所以一開始就要給予初始值。

ES6 - 新的函式表示方法

過去,我們是這樣宣告函式的:

function 函式名稱 (參數){
    函式定義
}

/* 或 */

var 函式名稱 = function (參數){
    函式定義
}

/* 舉例來說 */

var testFunction = function(A, B){
    return A+B;
}

然而這樣的宣告方法產生了一個問題:this這個關鍵字容易指向錯誤的位置,變成是常常會先用一個變數去接this,這樣很不方便,很麻煩。於是,新的東西就出現了:箭頭函式。在箭頭函式中this就不會出現指向錯誤位置的問題。

在箭頭函式中,我們用:

(多個參數) => {

}

/* 或是 */

一個參數 => {

}

取代了

function(參數){

}

也就是宣告方法為:

宣告型態 函式名稱 = (參數)=>{
    函式定義
}

/* 舉例來說 */

var testFunction = (A, B)=>{
    return A+B;
}

ES6 - spread operator「...」

「...」使用方法為...自訂名稱,它的用途是:
1.把陣列/物件展開成個別元素
2.或是用來複製一個陣列。

有的時候,我們並沒有辦法得知接受到的參數有幾個、有哪些。這個東西就解決了這個問題。

為什麼?

因為當我們把...名稱放在函式定義參數時,表示「我們要複製一個array」,丟入的參數就會被變成一個array。而使用它時再加上...,就又可以把它展開。這樣做,我們就能在不知道參數數量的情況下,接到所有參數。

const getAll=(...arg)=>{
    console.log(arg)
    console.log(...arg)
}
getAll("A","B",4,"我是C");
getAll("B",4,"我是C");

第一個console.log顯示我們把丟入的參數變成array。
第二個console.log就是把這個array展開後印出。

ES6 - 解決非同步問題:Promise

js的「非同步特性」是最常被提到的。「非同步」以簡單比喻方式來說,當今天有A、B兩函式被使用,如果A函式是「執行較慢的函式(EX: setTimeout,ajax)」,即使B函式在A函式後面才被使用,但網頁「不會等A函式做完,再執行B函式」,而是A開始後就開始執行B。以下示意圖(假設B的速度遠快於A)

但有的時候我們在B就會需要用到A處理後的資料。而Promise就是用來解決非同步所導致上述問題的工具。例如使用jQuery的ajax時,會等request做完之後才執行最後一個參數的function,也是同樣的概念。

跟jQuery有自己的語法一樣,Promise過去各家有各家的語法,而ES6將它做了統整。

運行的方法是讓「執行較慢的函式(EX: setTimeout)」做完後透過拋出一個resovle()告訴程式說

我做完囉~這是接下來你會用到的東西(resolve()中的參數),可以用這些東西做接下來的事囉哈哈哈哈

接到resovle和它傳來的參數後,再在then()中執行要接續執行的東西。以下是Promise的語法:

宣告型態 宣告名稱 = new Promise((resolve, reject)=>{
    定義要先做什麼事情
    resolve(參數);
})

宣告名稱.then((參數,由resolve丟出)=>{ 定義要後做什麼事情 }).catch((錯誤)=>{ 先做的事情出現錯誤時怎麼處理 }))

/* 通常會這樣排版*/

宣告名稱
.then((參數,由resolve丟出)=>{ 
    定義要後做什麼事情 
})
.catch((錯誤)=>{ 
    先做的事情出現錯誤時怎麼處理 
}))

ES6 - 解構賦值

過去如果我們要把一個array或物件的單一個別值指定給其他東西,必須寫迴圈或是用多個表示式。在ES6中,提供了一種可以一次處理完這件事的方法-解構賦值。例如在下面的範例中,我們先產生一個陣列後,再一次宣告兩個變數a和b,並同時把a被指定為apple,b被指定為banana。

const arr=[ "apple" , "banana" ];
const [ a , b ] = arr;

console.log("a is "+a); // a is apple
console.log("b is "+b); // b is banana

在下面的範例中,我們先產生一個物件,再一次宣告兩個變數a和b,並同時把a被指定為物件的fruitOne,b被指定為物件的fruitTwo。

const obj={ fruitOne: "apple", fruitTwo: "banana" };
const { fruitOne: a , fruitTwo: b  } = obj;

console.log("a is "+a); // a is apple
console.log("b is "+b); // b is banana

ES6 - 使用module分檔 (import & export)

ES6中,我們可以把js函式、變數、物件打包成模組,然後在其他js檔引入使用。方法是透過export在被模組化的檔案中設定要讓別人使用的東西,然後在要使用的地方import、給予一個名字,被輸出的檔案會變成一個物件,名稱是剛剛給予的名字,以物件方式存取就能使用。
例如現在有個hello.js:

export const helloWorld=()=>{
    console.log("hello world!");
}

export const msg="hello world";

我們就能在輸入檔中用{}以該函式、變數、物件的原名稱引入,直接去取出要的東西。
index.js

/*語法為 import + {名稱A, 名稱B, (...類推)} + from + 檔案路徑*/
import {helloWorld, msg} from "./hello.js";

helloWorld();
console.log(msg);

如果你希望引入後不要用原名稱,而是用其他自訂的名稱,那麼你可以這樣做:

import {helloWorld as myFun , msg as myMsg} from "./hello.js";

myFun();
console.log(myMsg);

另外也能把輸出的東西包成一個物件讓別人存取,輸入檔就能不用{}去取單一的東西,方法是加上default關鍵字、包成一個物件後,把要一起export的東西都丟進去這個物件。
輸出檔hello.js

const helloWorld=()=>{
    console.log("hello world!");
}

export const msg="hello world";

export default {helloWorld};
/*使用default必須要包成一個物件,export default可以和多個export同時存在。*/

在輸入檔中,我們只要給export default的物件一個名稱就能使用。
語法為:

import + 要指定給default輸出東西的名稱,{非default輸出東西的名稱A, (...類推)} + from + 檔案路徑;

index.js

import hello,{msg} from "./hello.js";
//如果沒有要用msg,可以只寫 import hello from "./hello.js";

hello.helloWorld();
console.log(msg);

From ES5 to ES6 - class

React的結構非常仰賴ES6的class。
因為很重要,不適合簡單帶過。在這裡我會希望讀者能先有任一程式語言class的知識,後面的文章中,我會說明ES6 class和c-like語言class不同、且在React中常用到的地方,但是還是建議先參閱版上其他專門對於ES6 class的教學,以了解細部的特性、規定。

小結: 其他ES5/ES6後新增的東西呢?

以上的這幾項,是我認為學習React很重要(或是我後面教學文會用到),但對於以「能實現目標」為導向的前端學習者可能會不知道的知識。 其他ES5後的新誕生的語法,雖然我認為在學習react上重要性沒那麼高,但對於簡化程式碼等狀況相當有幫助,未來在使用套件時也有可能會用到。

下一篇,我們就會從Hello world開始寫第一個react程式。


上一篇
【React.js入門 - 02】 環境設置(下) - 使用create-react-app
下一篇
【React.js入門 - 04】 HelloWorld! - 從ReactDOM開始
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
c3jack
iT邦新手 5 級 ‧ 2019-12-15 20:49:30

GOOD

0
照燒
iT邦新手 4 級 ‧ 2022-08-06 10:45:17

函數中
除了注意有些有阻塞特質
還有就是有些是靜態的

我要留言

立即登入留言